<?php
/**
 * Dependency service
 *
 * PHP version 5
 *
 * @category  W
 * @package   Services
 * @author    Olivier Hoareau <olivier@phppro.fr>
 * @copyright 2009 PHPPRO http://www.phppro.fr
 * @license   http://dev.phppro.fr/license PHPPRO
 * @version   SVN: $Id:$
 * @link      http://dev.phppro.fr/projects/wdependency
 */
require_once 'W/Dependency/Abstract.php';
require_once 'W/Dependency/Exception/Stop.php';
/**
 * Dependency service
 *
 * @category  W
 * @package   Services
 * @author    Olivier Hoareau <olivier@phppro.fr>
 * @copyright 2009 PHPPRO http://www.phppro.fr
 * @license   http://dev.phppro.fr/license PHPPRO
 * @version   SVN: $Id$
 * @link      http://dev.phppro.fr/projects/wdependency
 */
class W_Dependency_Service extends W_Dependency_Abstract
{
    /**
     * Adds an exporter
     *
     * @param string|W_Dependency_Exporter_Interface $exporter the exporter
     *
     * @return W_Dependency_Interface
     */
    public function addExporter ($exporter, $options = null)
    {
        $this->getAdapter()->addPlugin($exporter, $options, 'exporter');
        return $this;
    }
    /**
     * Adds a listener
     *
     * @param string|W_Dependency_Listener_Interface $listener the listener
     *
     * @return W_Dependency_Interface
     */
    public function addListener ($listener, $options = null)
    {
        $this->getAdapter()->addPlugin($listener, $options, 'listener');
        return $this;
    }
    /**
     * Adds a analyzer
     *
     * @param string|W_Dependency_Analyzer_Interface $analyzer the analyzer
     *
     * @return W_Dependency_Interface
     */
    public function addAnalyzer ($analyzer, $options = null)
    {
        $this->getAdapter()->addPlugin($analyzer, $options, 'analyzer');
        return $this;
    }
    /**
     * Adds a filter
     *
     * @param string|W_Dependency_Filter_Interface $filter the filter
     *
     * @return W_Dependency_Interface
     */
    public function addFilter ($filter, $options = null)
    {
        $this->getAdapter()->addPlugin($filter, $options, 'operator','W_Dependency_Filter');
        return $this;
    }
    /**
     * Adds a aggregator
     *
     * @param string|W_Dependency_Aggregator_Interface $aggregator the aggregator
     *
     * @return W_Dependency_Interface
     */
    public function addAggregator ($aggregator, $options = null)
    {
        $this->getAdapter()->addPlugin($aggregator, $options, 'operator','W_Dependency_Aggregator');
        return $this;
    }
    /**
     * Adds a source
     *
     * @param string|W_Dependency_Source_Interface $source the source
     *
     * @return W_Dependency_Interface
     */
    public function addSource ($source, $options = null)
    {
        $this->getAdapter()->addPlugin($source, $options, 'source');
        return $this;
    }
    /**
     * Adds a action
     *
     * @param string|W_Dependency_Action_Interface $action the action
     *
     * @return W_Action_Interface
     */
    public function addAction ($action, $options = null)
    {
        $this->getAdapter()->addPlugin($action, $options, 'action');
        return $this;
    }
    /**
     * Fires the specified event to the registered listeners
     *
     * @param string      $eventType the event type
     * @param array|mixed $eventData the event data
     *
     * @return array|null list of optional additional files to analyze
     */
    protected function fireEvent ($eventType, $eventData = null)
    {
        return $this->getAdapter()->fireEvent($eventType,$eventData);
    }
    /**
     * Analyzes directory dependencies
     *
     * @param $directory
     * @param $processed
     * @param $processing
     * @param $root
     * @param $recursive
     *
     * @return array dependency model
     */
    protected function analyzeDirectoryDependencies ($directory, $projectName, &$processed, &$processing, $root = null, $recursive = false)
    {
        $oldDirectory = $directory;
        $directory = W_W::fs()->getRealPath($directory);
        if (! $directory) {
            throw new RuntimeException("Directory not found '$oldDirectory'");
        }
        if (null === $root) {
            $root = $directory;
        }
        $this->fireEvent('enterDirectory', array('directory' => str_replace('\\','/',$directory) , 'root' => str_replace('\\','/',$root)));
        $files = scandir($directory);
        $extensions = array('php' , 'php3' , 'php4' , 'php5' , 'php6' , 'phtml' , 'inc');
        foreach ($files as $file) {
            if ('.' === $file || '..' === $file) {
                continue;
            }
            $realPath = W_W::fs()->getRealPath($directory . '/' . $file);
            if (true === W_W::fs()->isDirectory($realPath)) {
                $this->analyzeDirectoryDependencies($realPath, $projectName, $processed, $processing, $root, $recursive);
                continue;
            }
            $pos = strrpos($realPath, '.');
            if (false === $pos) {
                return '';
            }
            $extension = substr($realPath, $pos + 1);
            if (false === in_array($extension, $extensions)) {
                continue;
            }
            $processing[$realPath] = true;
            $this->analyzeFileDependencies($realPath, $projectName, $processed, $processing, $root, $recursive);
            if (false === array_key_exists($realPath, $processed)) {
                $processed[$realPath] = true;
            } else {
                $categories = $this->getFileCategories($realPath, $root);
                $this->fireEvent('alreadyProcessedFile', array('file' => $realPath , 'root' => $root , 'categories' => $categories));
            }
        }
        $this->fireEvent('exitDirectory', array('directory' => str_replace('\\','/',$directory) , 'root' => str_replace('\\','/',$root)));
        return $this;
    }
    /**
     * Analyzes the file dependencies
     *
     * @param $file
     * @param $processed
     * @param $processing
     * @param $root
     * @param $recursive
     *
     * @return array the dependency model
     */
    public function analyzeFileDependencies ($file, $projectName, &$processed, &$processing, $root = null, $recursive = false)
    {
        $oldFile = $file;
        $realPath = W_W::fs()->getRealPath($file);
        if (! $realPath) {
            throw new RuntimeException("File not found '$oldFile'");
        }
        $this->fireEvent('enterFile', array('file' => str_replace('\\','/',$realPath) , 'root' => str_replace('\\','/',$root)));
        $processing[$file] = true;
        $events = $this->getAdapter()->parseFile(array('item'=>$realPath,'root'=>$root,'project'=>$projectName));
        $files = array();

        foreach($events as $event) {
            $files = array_merge($files,$this->fireEvent($event['eventType'],$event['eventData']));
        }

        while (0 < count($files)) {
            $dependentFile = array_shift($files);
            // already processing ?
            if (true === array_key_exists($dependentFile, $processing)) {
                $files = array_merge($files, $this->fireEvent('alreadyProcessingFile', array('file' => $dependentFile , 'parentFile' => $realPath, 'project'=>$projectName)));
                continue;
            }
            // already processed ?
            if (true === array_key_exists($dependentFile, $processed)) {
                $files = array_merge($files, $this->fireEvent('alreadyProcessedFile', array('file' => $dependentFile , 'parentFile' => $realPath, 'project'=>$projectName)));
                continue;
            }
            $this->analyzeFileDependencies($dependentFile, $projectName, $processed, $processing, $root, $recursive);
        }
        $this->fireEvent('exitFile', array('file' => str_replace('\\','/',$realPath) , 'root' => str_replace('\\','/',$root) , 'project'=>$projectName));
        return $this;
    }
    public function process ($options)
    {
        // listeners
        $this->addListener(array('analyzers' , 'operators', 'exporters'));

        // other listeners
        $this->addListener(array('files' , 'includes' , 'inheritances' , 'instances' , 'statics' , 'types'));

        list($extraPlugins,$options) = $this->getAdapter()->listPluginsForOptions($options);
        foreach($extraPlugins as $pluginType => $extraPluginList) {
            $method = 'add'.ucfirst($pluginType);
            if (false === method_exists($this,$method)) {
                throw new RuntimeException("Unknown plugin type '$pluginType'");
            }
            foreach($extraPluginList as $pluginInfo) {
                $r = $this->$method($pluginInfo[0],$pluginInfo[1]);
            }
        }

        $this->fireEvent('start', array('includePaths' => isset($options['path']) ? $options['path'] : array()));
        if ('STOP' === $this->getAdapter()->executePlugins('action',array())) {
            W_W::log()->log("Stopping execution of plugin type '$pluginType'",'debug',__CLASS__);
            return $this;
        }

        $data = $this->getAdapter()->executePlugins('source',array());

        $path = isset($options['directory']) ? $options['directory'] : array();

        if (0 === count($data) && 0 === count($path)) {
            $this->getAdapter()->displayUsage();
            return $this;
        }

        $oldPath = array();
        foreach(array_keys($path) as $pathKey) {
            $oldPath[$pathKey] = $path[$pathKey];
            $path[$pathKey] = W_W::fs()->getRealPath($path[$pathKey]);
            if (! $path[$pathKey]) {
                throw new RuntimeException("File or directory not found '{$oldPath[$pathKey]}'");
            }
        }
        if (null === $data || 0 === count($data)) {
            $this->fireEvent('enterMultiParsers',array('paths'=>$path));
            foreach(array_keys($path) as $pathKey) {
                if (is_numeric($pathKey)) {
                    $path[basename($path[$pathKey])] = $path[$pathKey];
                    unset($path[$pathKey]);
                }
            }
            foreach($path as $pathKey => $pathValue) {
                $projectName = $pathKey;
                $root = $pathValue;
                $data = $options['path'];
                if (null === $data) {
                    $data = array();
                }
                if (false === is_array($data)) {
                    $data = explode(PATH_SEPARATOR, $data);
                }
                $includePaths = $data;
                $processed = array();
                $processing = array();
                $oldPath = $root;
                $path = W_W::fs()->getRealPath($root);
                if (! $path) {
                    throw new RuntimeException("File or directory not found '$oldPath'");
                }
                $this->fireEvent('enterParsers', array('includePaths' => $includePaths));
                if (true === is_dir($path)) {
                    $this->analyzeDirectoryDependencies($path, $projectName, $processed, $processing, $path, true);
                } elseif (true === W_W::fs()->isExistingFile($path)) {
                    if (false === W_W::fs()->isDirectory($path)) {
                        $this->analyzeFileDependencies($path, $projectName, $processed, $processing, $path, true);
                    }
                } else {
                    throw new RuntimeException("File or directory '$path' not found");
                }
                $this->fireEvent('exitParsers',array('project'=>$projectName));
            }
            $this->fireEvent('exitMultiParsers');
            $data = $this->getAdapter()->getCollectedPluginsData();
        }
        // analyzers
        $data = $this->getAdapter()->executePlugins('analyzer',array('data'=>$data,'root'=>$path));

        // operators (filters,aggregators,...)
        foreach(array_keys($data) as $key) {
            $data[$key] = $this->getAdapter()->executePlugins('operator',  array('data'=>$data[$key],'root'=>$path), false);
        }
        // exporters
        $data = $this->getAdapter()->executePlugins('exporter',array('data'=>$data,'root'=>$path));

        $this->fireEvent('stop');
        return $this;
    }
    public function parseCommandLineArguments($argv)
    {
        return $this->getAdapter()->parseCommandLineArguments($argv);
    }
}
